package com.huayu.languo.common.configurer;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import com.huayu.languo.model.dto.ResultDto;
import com.huayu.languo.model.enums.ResultCode;
import com.huayu.languo.model.excp.BizException;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

/**
 TODO  使用方式
        http://webfuse.cn/2019/01/20/WebMvcConfigurationSupport%E5%92%8CWebMvcConfigurer/
        https://www.cnblogs.com/cl-rr/p/9632886.html
          使用@EnableWebMvc 注解 等于 扩展了WebMvcConfigurationSupport，但是没有重写任何方法
          使用“extends WebMvcConfigurationSupport”方式（需要添加@EnableWebMvc），会屏蔽掉springBoot的@EnableAutoConfiguration中的设置
          使用“implement WebMvcConfigurer”可以配置自定义的配置，同时也使用了@EnableAutoConfiguration中的设置
          使用“implement WebMvcConfigurer + @EnableWebMvc”，会屏蔽掉springBoot的@EnableAutoConfiguration中的设置
          这里的“@EnableAutoConfiguration中的设置”是指，读取 application2222.properties 或 application.yml 文件中的配置。
          所以，如果需要使用springBoot的@EnableAutoConfiguration中的设置，那么就只需要“implement WebMvcConfigurer”即可。
          如果，需要自己扩展同时不使用@EnableAutoConfiguration中的设置，可以选择另外的方式。
 TODO
         有时候我们需要自己定制一些项目的设置，可以有以下几种使用方式：
         @EnableWebMvc + extends WebMvcConfigurationAdapter，在扩展的类中重写父类的方法即可，这种方式会屏蔽springboot的@EnableAutoConfiguration中的设置
         extends WebMvcConfigurationSupport，在扩展的类中重写父类的方法即可，这种方式会屏蔽springboot的@EnableAutoConfiguration中的设置
         extends WebMvcConfigurationAdapter/ implement WebMvcConfigurer，在扩展的类中重写父类的方法即可，这种方式依旧使用springboot的@EnableAutoConfiguration中的设置


*/

/** https://www.dhbin.cn/p/Spring-boot-WebMvcConfigurer.html
 *  方法签名	解释
 configurePathMatch(PathMatchConfigurer configurer)	配置URL匹配规则
 configureContentNegotiation(ContentNegotiationConfigurer configurer)	配置内容协商机制，一个路径可以返回多种数据格式
 configureAsyncSupport(AsyncSupportConfigurer configurer)	配置异步任务处理
 configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer)	DefaultServletHandlerConfigurer用一个enable()方法，使对静态资源的请求转发到servlet容器上，而不是控制器
 addFormatters(FormatterRegistry registry)	配置类型转换，一般用于字符串与对象间的转换
 addInterceptors(InterceptorRegistry registry)	配置拦截器
 addResourceHandlers(ResourceHandlerRegistry registry)	配置静态资源处理策略
 addCorsMappings(CorsRegistry registry)	配置跨域
 addViewControllers(ViewControllerRegistry registry)	可以方便的实现一个请求直接映射成视图，而无需书写controller
 configureViewResolvers(ViewResolverRegistry registry)	配置视图解析器
 addArgumentResolvers(List< HandlerMethodArgumentResolver> resolvers)	配置参数解析器，比如添加一个@JsonParam，解析JSON字段注入方法的参数中
 addReturnValueHandlers(List< HandlerMethodReturnValueHandler> handlers)	配置对Controller返回结果进一步处理
 configureMessageConverters(List<HttpMessageConverter<?>> converters)
 configureHandlerExceptionResolvers(List< HandlerExceptionResolver> resolvers)
 extendHandlerExceptionResolvers(List< HandlerExceptionResolver> resolvers)
 configureMessageConverters：配置消息转换器。重载会覆盖默认注册的HttpMessageConverter
 extendMessageConverters：配置消息转换器。仅添加一个自定义的HttpMessageConverter.
 configureHandlerExceptionResolvers：配置异常转换器
 extendHandlerExceptionResolvers：添加异常转化器

 addArgumentResolvers：添加自定义方法参数处理器
 需求：想要在Controller的参数列表中注入当前回话的自定义PlatformSession对象。

 
 * Spring MVC 配置
 */
@Configuration
public class AppWebMvcConfigurer implements WebMvcConfigurer {

    private final Logger logger = LoggerFactory.getLogger(WebMvcConfigurer.class);

    // 当前激活的配置文件
    @Value("${spring.profiles.active}")
    private String env;


    //@Override
    //public void addViewControllers(ViewControllerRegistry registry) {
    //    registry.addViewController("/error").setViewName("error.html");
    //    registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
    //}
    //
    //@Override
    //public void configurePathMatch(PathMatchConfigurer configurer) {
    //    super.configurePathMatch(configurer);
    //    configurer.setUseSuffixPatternMatch(false);
    //}

    //@Override
    //public void addViewControllers(ViewControllerRegistry registry) {
    //    // 对 "/hello" 的 请求 redirect 到 "/home"
    //    registry.addRedirectViewController("/hello", "/home");
    //    // 对 "/admin/**" 的请求 返回 404 的 http 状态
    //    registry.addStatusController("/admin/**", HttpStatus.NOT_FOUND);
    //    // 将 "/home" 的 请求响应为返回 "home" 的视图
    //    registry.addViewController("/home").setViewName("home");
    //}



    // 使用阿里 FastJson 作为JSON MessageConverter
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
        FastJsonConfig config = new FastJsonConfig();
        config.setSerializerFeatures(SerializerFeature.WriteMapNullValue, // 保留空的字段
                         SerializerFeature.WriteNullStringAsEmpty, // String null -> ""
                         SerializerFeature.WriteEnumUsingToString,//设置WriteEnumUsingToString
                         SerializerFeature.WriteNullNumberAsZero);// Number null -> 0
        converter.setFastJsonConfig(config);

        converter.setDefaultCharset(Charset.forName("UTF-8"));
        List<MediaType> supportedMediaTypes = new ArrayList<>();
        supportedMediaTypes.add(MediaType.APPLICATION_JSON);
        supportedMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
        supportedMediaTypes.add(MediaType.APPLICATION_ATOM_XML);
        supportedMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
        supportedMediaTypes.add(MediaType.APPLICATION_OCTET_STREAM);
        supportedMediaTypes.add(MediaType.APPLICATION_PDF);
        supportedMediaTypes.add(MediaType.APPLICATION_RSS_XML);
        supportedMediaTypes.add(MediaType.APPLICATION_XHTML_XML);
        supportedMediaTypes.add(MediaType.APPLICATION_XML);
        supportedMediaTypes.add(MediaType.IMAGE_GIF);
        supportedMediaTypes.add(MediaType.IMAGE_JPEG);
        supportedMediaTypes.add(MediaType.IMAGE_PNG);
        supportedMediaTypes.add(MediaType.TEXT_EVENT_STREAM);
        supportedMediaTypes.add(MediaType.TEXT_HTML);
        supportedMediaTypes.add(MediaType.TEXT_MARKDOWN);
        supportedMediaTypes.add(MediaType.TEXT_PLAIN);
        supportedMediaTypes.add(MediaType.TEXT_XML);
        converter.setSupportedMediaTypes(supportedMediaTypes);
        converters.add(converter);
    }



    // 统一异常处理
    @Override
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {

        exceptionResolvers.add(new HandlerExceptionResolver() {

            @Override
            public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) {
                ResultDto result = null;
                if (e instanceof BizException) {// 业务失败的异常，如“账号或密码错误”
                    result = ResultDto.builder().code(ResultCode.BIZ_FAIL_420.code()).msg(e.getMessage()).build();
                    logger.info(e.getMessage());
                } else if (e instanceof NoHandlerFoundException) {
                    result = ResultDto.builder().code(ResultCode.NOT_FOUND_404.code()).msg("接口 [" + request.getRequestURI() + "] 不存在").build();
                } else if (e instanceof ServletException) {
                    result = ResultDto.builder().code(ResultCode.BIZ_FAIL_420.code()).msg(e.getMessage()).build();
                } else if (e instanceof ConstraintViolationException) {
                    ConstraintViolationException exs = (ConstraintViolationException) e;
                    Set<ConstraintViolation<?>> violations = exs.getConstraintViolations();
                    List<String> list = new ArrayList<>();
                    for (ConstraintViolation<?> item : violations) {
                        /**打印验证不通过的信息*/
                        System.out.println(item.getMessage());
                        list.add(item.getMessage());
                    }
                    result = ResultDto.builder().code(ResultCode.BIZ_FAIL_420.code()).msg(String.join(",", list)).build();
                } else {
                    result = ResultDto.builder().code(ResultCode.SERVER_ERROR_500.code()).msg("接口 [" + request.getRequestURI() + "] 内部错误，请联系管理员").build();
                    String message;
                    if (handler instanceof HandlerMethod) {
                        HandlerMethod handlerMethod = (HandlerMethod) handler;
                        message = String.format("接口 [%s] 出现异常，方法：%s.%s，异常摘要：%s", request.getRequestURI(), handlerMethod.getBean().getClass().getName(),
                                        handlerMethod.getMethod().getName(), e.getMessage());
                    } else {
                        message = e.getMessage();
                    }
                    logger.error(message, e);
                }
                responseResult(response, result);
                return new ModelAndView();
            }
        });
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 通过 "/home.html" 请求, 来访问 /resource/static/home.html 静态资源
        //registry.addResourceHandler("/home.html").addResourceLocations("classpath:/static/home.html");
        // swagger 配置
        registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
        registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }

    //解决跨域问题
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**") // **代表所有路径
                        .allowedOrigins("*") // allowOrigin指可以通过的ip，*代表所有，可以使用指定的ip，多个的话可以用逗号分隔，默认为*
                        .allowedMethods("GET", "POST", "PUT", "OPTIONS", "DELETE", "PATCH") // 指请求方式 默认为*
                        .allowCredentials(true) // 支持证书，默认为true
                        .maxAge(3600) // 最大过期时间，默认为-1
                        .allowedHeaders("*");
    }


    // 添加拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 接口签名认证拦截器，该签名认证比较简单，实际项目中可以使用Json Web Token或其他更好的方式替代。
        if (!"dev".equals(StringUtils.isEmpty(env) ? "" : env.trim())) { // 开发环境忽略签名认证
            registry.addInterceptor(new HandlerInterceptorAdapter() {

                @Override
                public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                    // 验证签名
                    // 可以根据 getIpAddress(request) 做一层Ip过滤
                    boolean pass = validateSign(request);
                    if (pass) {
                        return true;
                    } else {
                        logger.warn("签名认证失败，请求接口：{}，请求IP：{}，请求参数：{}", request.getRequestURI(), getIpAddress(request), JSON.toJSONString(request.getParameterMap()));

                        ResultDto res = ResultDto.builder().code(ResultCode.UNAUTHORIZED_401.code()).msg("签名认证失败").build();
                        responseResult(response, res);
                        return false;
                    }
                }
            });
        }
    }


    private void responseResult(HttpServletResponse response, ResultDto result) {
        response.setCharacterEncoding("UTF-8");
        response.setHeader("Content-type", "application/json;charset=UTF-8");
        response.setStatus(200);
        try {
            response.getWriter().write(JSON.toJSONString(result));
        } catch (IOException ex) {
            logger.error(ex.getMessage());
        }
    }

    /**
     * 一个简单的签名认证，规则： 1. 将请求参数按ascii码排序 2. 拼接为a=value&b=value...这样的字符串（不包含sign） 3.
     * 混合密钥（secret）进行md5获得签名，与请求的签名进行比较
     */
    private boolean validateSign(HttpServletRequest request) {
        String requestSign = request.getParameter("sign");// 获得请求签名，如sign=19e907700db7ad91318424a97c54ed57
        if (StringUtils.isEmpty(requestSign)) {
            return false;
        }
        List<String> keys = new ArrayList<>(request.getParameterMap().keySet());
        keys.remove("sign");// 排除sign参数
        Collections.sort(keys);// 排序

        StringBuilder sb = new StringBuilder();
        for (String key : keys) {
            sb.append(key).append("=").append(request.getParameter(key)).append("&");// 拼接字符串
        }
        String linkString = sb.toString();
        linkString = StringUtils.substring(linkString, 0, linkString.length() - 1);// 去除最后一个'&'

        String secret = "Potato";// 密钥，自己修改
        String sign = DigestUtils.md5Hex(linkString + secret);// 混合密钥md5

        return StringUtils.equals(sign, requestSign);// 比较
    }

    private String getIpAddress(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        // 如果是多级代理，那么取第一个ip为客户端ip
        if (ip != null && ip.indexOf(",") != -1) {
            ip = ip.substring(0, ip.indexOf(",")).trim();
        }

        return ip;
    }

    //@Bean
    //public EnumConverterFactory enumConverterFactory() {
    //    return new EnumConverterFactory();
    //}


    /**
     * 注册自定义的Formatter和Convert,例如, 对于日期类型,枚举类型的转化.
     * 不过对于日期类型,使用更多的是使用
     *  @DateTimeFormat(pattern = "yyyy-MM-dd")
     *  private Date createTime;
     */
    //@Override
    //public void addFormatters(FormatterRegistry registry) {
    //    //super.addFormatters(registry);
    //    //registry.addConverterFactory(enumConverterFactory());
    //}

}
